#include <stdbool.h>
#include <stddef.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

#include "lista.h"

/********************************************************************
*                             Prototipos
*********************************************************************/

typedef struct nodo_lista nodo_lista_t;
nodo_lista_t * nodo_lista_crear(void *);
void nodo_lista_destruir(nodo_lista_t *);
bool lista_iter_al_principio(lista_iter_t *);

/********************************************************************
*                             Implementacion
*********************************************************************/

struct nodo_lista{
    void * dato;
    nodo_lista_t * siguiente;
};

struct lista{
    size_t longitud;
    nodo_lista_t * primero;
    nodo_lista_t * ultimo;
};

struct lista_iter{
    nodo_lista_t * anterior;
    nodo_lista_t * actual;
};

// Crea una lista
// Post: devuelve una nueva lista vacía
lista_t *lista_crear()
{
    lista_t * lista = malloc(sizeof(lista_t));

    if(lista == NULL)
        return NULL;

    lista->longitud = 0;
    lista->primero = NULL;
    lista->ultimo = NULL;

    return lista;
}

// Destruye la lista. Si se recibe la función destruir_dato por parámetro,
// para cada uno de los elementos de la lista llama a destruir_dato.
// Pre: la lista fue creada, la función destruir_dato es una función capaz de destruir
// los datos de la lista, o NULL en caso de que no se la utilice.
// Post: se eliminaron todos los elementos de la listas.
void lista_destruir(lista_t *lista, void destruir_dato(void *))
{
    while(!lista_esta_vacia(lista))
    {
        if(destruir_dato == NULL)
            lista_borrar_primero(lista);
        else
            destruir_dato(lista_borrar_primero(lista));

    }
    free(lista);
}

// Devuelve verdadero o falso, según si la lista tiene o no elementos enlazados.
// Pre: la lista fue creada.
bool lista_esta_vacia(const lista_t *lista)
{
    return lista->longitud == 0;
}

// Agrega un nuevo elemento a la lista. Devuelve falso en caso de error.
// Pre: la lista fue creada.
// Post: se agregó un nuevo elemento a la lista y se encuentra al principio.
bool lista_insertar_primero(lista_t *lista, void *dato)
{
    nodo_lista_t *nuevo_nodo = nodo_lista_crear(dato);

    if(!nuevo_nodo)
        return false;

    if(lista_esta_vacia(lista))
    {
        lista->primero = nuevo_nodo;
        lista->ultimo = nuevo_nodo;
        lista->longitud++;
        return true;
    }

    nuevo_nodo->siguiente = lista->primero;
    lista->primero = nuevo_nodo;

    lista->longitud++;
    return true;
}

nodo_lista_t * nodo_lista_crear(void *dato)
{
    nodo_lista_t * nodo = malloc(sizeof(nodo_lista_t));

    if(nodo == NULL)
        return NULL;

    nodo->dato = dato;
    nodo->siguiente = NULL;
    return nodo;
}

void nodo_lista_destruir(nodo_lista_t * nodo)
{
    free(nodo);
}

// Devuelve el contenido del primer elemento de la lista y lo elimina.
// Si lista está vacía devuelve NULL.
// Pre: la lista fue creada.
// Post: se devolvió el contenido del primer elemento de la lista y se lo eliminó.
void * lista_borrar_primero(lista_t *lista)
{
    if(lista_esta_vacia(lista))
        return NULL;

    nodo_lista_t * nodo_primero_extraido = lista->primero;

    lista->primero = nodo_primero_extraido->siguiente;

    lista->longitud--;

    if(lista->primero == NULL)
        lista->ultimo = NULL;

    void * dato = nodo_primero_extraido->dato;

    nodo_lista_destruir(nodo_primero_extraido);

    return dato;
}

// Obtiene el valor del primer elemento de la lista. Si la lista tiene
// elementos, se devuelve el valor del primero, si está vacía devuelve NULL.
// Pre: la lista fue creada.
// Post: se devolvió el primer elemento de la lista, cuando no está vacía.
void *lista_ver_primero(const lista_t *lista)
{
    if(lista_esta_vacia(lista))
        return NULL;

    return lista->primero->dato;
}

// Devuelve la cantidad de elementos que tiene la lista.
// Pre: la lista fue creada.
// Post: devuelve la canitdad de elementos que tiene la lista.
size_t lista_largo(const lista_t *lista)
{
    return lista->longitud;
}

// Agrega un nuevo elemento a la lista. Devuelve falso en caso de error.
// Pre: la lista fue creada.
// Post: se agregó un nuevo elemento a la lista y se encuentra al final.
bool lista_insertar_ultimo(lista_t *lista, void *dato)
{
    if(lista_esta_vacia(lista))
        return lista_insertar_primero(lista,dato);

    nodo_lista_t * nuevo_nodo = nodo_lista_crear(dato);

    if(nuevo_nodo == NULL)
        return false;

    // Agrego esta linea para intentar ajustar un error de Valgrind
    if(lista->ultimo == NULL)
        return false;

    lista->ultimo->siguiente = nuevo_nodo;
    lista->ultimo = nuevo_nodo;
    lista->longitud++;

    return true;
}

// Crea un iterador.
// Post: devuelve un nuevo iterador.
lista_iter_t *lista_iter_crear(const lista_t *lista)
{
    lista_iter_t *iter = malloc(sizeof(lista_iter_t));

    if(!iter)
        return NULL;

    iter->anterior = NULL;
    iter->actual = lista->primero;

    return iter;
}

// Pre: el iterador fue creado.
// Post: muestra el contenido de la posicion del iterador.
void *lista_iter_ver_actual(const lista_iter_t *iter)
{
    if(lista_iter_al_final(iter))
        return NULL;

    return iter->actual->dato;
}

// Pre: el iterador fue creado.
// Post: destruye el iterador.
void lista_iter_destruir(lista_iter_t *iter)
{
    free(iter);
}

// Pre: el iterador fue creado.
// Post: avanza un nodo en la iteracion.
bool lista_iter_avanzar(lista_iter_t *iter)
{
    if(lista_iter_al_final(iter))
        return false;

    iter->anterior = iter->actual;
    iter->actual = iter->actual->siguiente;

    return true;
}

// Pre: el iterador fue creado.
// Post: devuelve verdadero si el iterador se encuentra apuntando al final de la lista.
bool lista_iter_al_final(const lista_iter_t *iter)
{
    return (iter->actual == NULL);
}

// Inserta un elemento en la lista en la posicion del iterador.
// Pre: la lista fue creada, el iterador fue creado.
// Post: devuelve verdadero si el dato se ingreso en la posicion del iterador
// y el iterador quedo apuntando a ese nuevo dato.
// Caso contrario devuelve falso.
bool lista_insertar(lista_t *lista, lista_iter_t *iter, void *dato)
{
    if(lista_esta_vacia(lista))
    {
        if(!lista_insertar_primero(lista,dato))
            return false;

        iter->actual = lista->primero;
        iter->anterior = NULL;
        return true;
    }

    nodo_lista_t * nuevo_nodo = nodo_lista_crear(dato);

    if(nuevo_nodo == NULL)
        return false;

    if(lista_iter_al_final(iter))
        lista->ultimo = nuevo_nodo;

    if(lista_iter_al_principio(iter))
    {
        lista->primero = nuevo_nodo;
        nuevo_nodo->siguiente = iter->actual;
        iter->actual = nuevo_nodo;
        lista->longitud++;
        return true;
    }

    iter->anterior->siguiente = nuevo_nodo;
    nuevo_nodo->siguiente = iter->actual;
    iter->actual = nuevo_nodo;

    lista->longitud++;

    return true;
}

// Elimina un elemento en la posicion del iterador.
// Pre: la lista fue creada, el iterador fue creado.
// Post: devuelve el dato apuntado por el elemento eliminado, NULL en otro caso.
void *lista_borrar(lista_t *lista, lista_iter_t *iter)
{
    if(lista_esta_vacia(lista))
        return NULL;

    if(lista_iter_al_final(iter))
        return NULL;

    nodo_lista_t * nodo_a_borrar = iter->actual;
    void * dato = nodo_a_borrar->dato;

    // Chequeo si tengo que actualizar el primero de la lista
    if(lista_iter_al_principio(iter))
        lista->primero = nodo_a_borrar->siguiente;
    else
        iter->anterior->siguiente = nodo_a_borrar->siguiente;

    iter->actual = nodo_a_borrar->siguiente;

    // Chequeo si tengo que actualizar el ultimo de la lista
    if(iter->actual == NULL)
        lista->ultimo = iter->anterior;

    nodo_lista_destruir(nodo_a_borrar);

    lista->longitud --;

    return dato;
}

bool lista_iter_al_principio(lista_iter_t * iter)
{
    return iter->anterior == NULL;
}

// Pre: la lista fue creada
// Post: a cada elemento de la lista se le aplica la funcion visitar.
// Opcionalmente se puede usar el parametro extra para mas opciones.
void lista_iterar(lista_t *lista, bool (*visitar)(void *dato, void *extra), void *extra)
{
    if(lista_esta_vacia(lista))
        return;

    nodo_lista_t *nodo_aux = lista->primero;

    bool ok;

    do
    {
        if((ok = visitar(nodo_aux->dato,extra)))
            nodo_aux = nodo_aux->siguiente;

    }while((nodo_aux != NULL) && (ok));

    return;
}

